﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="RecentFileList.cs" >
//   J. Prinz
// </copyright>
// <summary>
//   This class encapsulates the logic for a recent file list.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace AutomationMLMapper.Utilities
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Collections.Specialized;
    using System.ComponentModel;
    using System.Linq;
    using System.Xml;
    using System.Xml.Schema;
    using System.Xml.Serialization;

    /// <summary>
    ///   This class encapsulates the logic for a recent file list.
    /// </summary>
    /// <remarks>
    ///   This class can be used in a Settings file to store and load the recent file list as user settings. In Visual Studio you might need
    ///   to enter the full class name "System.Waf.Applications.RecentFileList" in the "Select a type" dialog.
    /// </remarks>
    public sealed class RecentFileList : IXmlSerializable
    {
        #region Fields

        /// <summary>
        ///  The read only recent files.
        /// </summary>
        private readonly ReadOnlyObservableCollection<RecentFile> readOnlyRecentFiles;

        /// <summary>
        ///  The recent files.
        /// </summary>
        private readonly ObservableCollection<RecentFile> recentFiles;

        /// <summary>
        ///  The max files number in the list
        /// </summary>
        private int maxFilesNumber = 8;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="RecentFileList"/> class. 
        /// </summary>
        public RecentFileList()
        {
            this.recentFiles = new ObservableCollection<RecentFile>();
            this.readOnlyRecentFiles = new ReadOnlyObservableCollection<RecentFile>(this.recentFiles);
            this.recentFiles.CollectionChanged += this.RecentFilesCollectionChanged;
        }

        #endregion

        #region Public Events

        /// <summary>
        ///  The collection of Recent Files changed.
        /// </summary>
        public event NotifyCollectionChangedEventHandler CollectionChanged;

        #endregion

        #region Public Properties

        /// <summary>
        ///   Gets or sets the maximum number of recent files in the list.
        /// </summary>
        /// <remarks>
        ///   If the set number is lower than the list count then the recent file list is truncated to the number.
        /// </remarks>
        public int MaxFilesNumber
        {
            get
            {
                return this.maxFilesNumber;
            }

            set
            {
                if (this.maxFilesNumber != value)
                {
                    if (value <= 0)
                    {
                        throw new ArgumentException("The value must be equal or larger than 1.");
                    }

                    this.maxFilesNumber = value;

                    if (this.recentFiles.Count - this.maxFilesNumber >= 1)
                    {
                        this.RemoveRange(
                            this.recentFiles, this.maxFilesNumber, this.recentFiles.Count - this.maxFilesNumber);
                    }
                }
            }
        }

        /// <summary>
        ///   Gets the list of recent files.
        /// </summary>
        public ReadOnlyObservableCollection<RecentFile> RecentFiles
        {
            get
            {
                return this.readOnlyRecentFiles;
            }
        }

        #endregion

        #region Properties

        /// <summary>
        ///   Gets the pin count.
        /// </summary>
        private int PinCount
        {
            get
            {
                return this.recentFiles.Count(r => r.IsPinned);
            }
        }

        #endregion

        #region Public Methods and Operators

        /// <summary>
        /// Adds a file to the recent file list. If the file already exists in the list then it only changes the position in the list.
        /// </summary>
        /// <param name="fileName">
        /// The path of the recent file. 
        /// </param>
        /// <returns>
        /// The AutomationMLMapper.Utilities.RecentFile. 
        /// </returns>
        public RecentFile AddFile(string fileName)
        {
            if (string.IsNullOrEmpty(fileName))
            {
                throw new ArgumentException("The argument fileName must not be null or empty.");
            }

            var recentFile = this.recentFiles.FirstOrDefault(r => r.Path == fileName);

            if (recentFile != null)
            {
                var oldIndex = this.recentFiles.IndexOf(recentFile);
                var newIndex = recentFile.IsPinned ? 0 : this.PinCount;
                if (oldIndex != newIndex)
                {
                    this.recentFiles.Move(oldIndex, newIndex);
                }
            }
            else
            {
                if (this.PinCount < this.maxFilesNumber)
                {
                    if (this.recentFiles.Count >= this.maxFilesNumber)
                    {
                        this.RemoveAt(this.recentFiles.Count - 1);
                    }

                    this.Insert(this.PinCount, new RecentFile(fileName));
                }
            }

            return recentFile;
        }

        /// <summary>
        /// Loads the specified collection into the recent file list. Use this method when you need to initialize the RecentFileList 
        ///   manually. This can be useful when you are using an own persistence implementation.
        /// </summary>
        /// <remarks>
        /// Recent file items that exist before the Load method is called are removed.
        /// </remarks>
        /// <param name="recentFilesCollection">
        /// The recent files. 
        /// </param>
        public void Load(IEnumerable<RecentFile> recentFilesCollection)
        {
            if (recentFilesCollection == null)
            {
                throw new ArgumentNullException("recentFilesCollection");
            }

            this.Clear();
            this.AddRange(this.recentFiles, recentFilesCollection.Take(this.maxFilesNumber));
        }

        #endregion

        #region Explicit Interface Methods

        /// <summary>
        /// IXmlSerializable - Interface Method
        /// </summary>
        /// <returns> The System.Xml.Schema.XmlSchema. </returns>
        XmlSchema IXmlSerializable.GetSchema()
        {
            return null;
        }

        /// <summary>
        /// The read xml for deserialization of recent file List.
        /// </summary>
        /// <param name="reader">
        /// XML reader. 
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// Parameter should not be null.
        /// </exception>
        void IXmlSerializable.ReadXml(XmlReader reader)
        {
            if (reader == null)
            {
                throw new ArgumentNullException("reader");
            }

            reader.ReadToDescendant("RecentFile");
            while (reader.MoveToContent() == XmlNodeType.Element && reader.LocalName == "RecentFile")
            {
                var recentFile = new RecentFile();
                ((IXmlSerializable)recentFile).ReadXml(reader);
                this.Add(recentFile);
            }

            if (!reader.IsEmptyElement)
            {
                reader.ReadEndElement();
            }
        }

        /// <summary>
        /// The XmlWrite used for Serialization.
        /// </summary>
        /// <param name="writer">
        /// The writer. 
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// Argument is null.
        /// </exception>
        void IXmlSerializable.WriteXml(XmlWriter writer)
        {
            if (writer == null)
            {
                throw new ArgumentNullException("writer");
            }

            foreach (var recentFile in this.recentFiles)
            {
                writer.WriteStartElement("RecentFile");
                ((IXmlSerializable)recentFile).WriteXml(writer);
                writer.WriteEndElement();
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Add a File to the RecentFileList
        /// </summary>
        /// <param name="recentFile">
        /// The recent file. 
        /// </param>
        private void Add(RecentFile recentFile)
        {
            recentFile.PropertyChanged += this.RecentFilePropertyChanged;
            this.recentFiles.Add(recentFile);
        }

        /// <summary>
        /// Add a range of Files
        /// </summary>
        /// <param name="collection">
        /// The collection. 
        /// </param>
        /// <param name="itemsToAdd">
        /// The items to add. 
        /// </param>
        private void AddRange(ICollection<RecentFile> collection, IEnumerable<RecentFile> itemsToAdd)
        {
            foreach (var item in itemsToAdd)
            {
                item.PropertyChanged += this.RecentFilePropertyChanged;
                collection.Add(item);
            }
        }

        /// <summary>
        /// Clear the RecentFileLIst.
        /// </summary>
        private void Clear()
        {
            foreach (var recentFile in this.recentFiles)
            {
                recentFile.PropertyChanged -= this.RecentFilePropertyChanged;
            }

            this.recentFiles.Clear();
        }

        /// <summary>
        /// Insert REcentFile at Index
        /// </summary>
        /// <param name="index">
        /// The index. 
        /// </param>
        /// <param name="recentFile">
        /// The recent file. 
        /// </param>
        private void Insert(int index, RecentFile recentFile)
        {
            recentFile.PropertyChanged += this.RecentFilePropertyChanged;
            this.recentFiles.Insert(index, recentFile);
        }

        /// <summary>
        /// The recent file property changed.
        /// </summary>
        /// <param name="sender">
        /// The sender. 
        /// </param>
        /// <param name="e">
        /// The e. 
        /// </param>
        private void RecentFilePropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "IsPinned")
            {
                var recentFile = (RecentFile)sender;
                var oldIndex = this.recentFiles.IndexOf(recentFile);
                if (recentFile.IsPinned)
                {
                    this.recentFiles.Move(oldIndex, 0);
                }
                else
                {
                    var newIndex = this.PinCount;
                    this.recentFiles.Move(oldIndex, newIndex);
                }
            }
        }

        /// <summary>
        /// The recent files_ collection changed.
        /// </summary>
        /// <param name="sender">
        /// The sender. 
        /// </param>
        /// <param name="e">
        /// The e. 
        /// </param>
        private void RecentFilesCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (this.CollectionChanged != null)
            {
                this.CollectionChanged(this, e);
            }
        }

        /// <summary>
        /// Remove File at Index
        /// </summary>
        /// <param name="index">
        /// The index. 
        /// </param>
        private void RemoveAt(int index)
        {
            this.recentFiles[index].PropertyChanged -= this.RecentFilePropertyChanged;
            this.recentFiles.RemoveAt(index);
        }

        /// <summary>
        /// Remove range of "count" Items, starting at index
        /// </summary>
        /// <param name="collection">
        /// The collection. 
        /// </param>
        /// <param name="index">
        /// The index. 
        /// </param>
        /// <param name="count">
        /// The count. 
        /// </param>
        private void RemoveRange(ObservableCollection<RecentFile> collection, int index, int count)
        {
            for (var i = 0; i < count; i++)
            {
                collection[index].PropertyChanged -= this.RecentFilePropertyChanged;
                collection.RemoveAt(index);
            }
        }

        #endregion
    }
}